﻿using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace DIExample
{   //【1】依赖注入是请求服务的时候去生成对象,而不是注入的时候就生成对象了
    //【2】长生命周期的服务不能依赖于短的(注册为瞬时的服务，其构造方法依赖的服务可以是单例的，反之则不行)
    //单例Singleton：第一被请求时创建实例,每次请求返回的都是同一个实例
    //瞬时Transient：每次向容器请求服务时都会创建一个全新的对象。
    //作用域Scoped：同一个作用域内是单例，不同作用域是不同的。
    internal class Program
    {
        static void Main(string[] args)
        {
            //【1】构造容器的生成类
            ServiceCollection serviceCollection = new ServiceCollection();
            //【2】注册容器里的服务信息。生成容器前注入服务才能实现自动装配
            //没有主动创建Scope,从根容器解析AddScoped服务时会复用同一个隐式创建的根级Scope(表现为单例)
            serviceCollection.AddTransient(typeof(IVechicle), typeof(Car));
            //多次注入同一个类型的服务会覆盖掉旧的
            serviceCollection.AddTransient<IVechicle, Car>();
            //TryAddScoped:如果注册过了如IVechicle,就不再重复注册
            serviceCollection.TryAddTransient<IVechicle, Tank>();
            //会使用scoped覆盖掉上面Transient的IVechicle
            serviceCollection.AddScoped<IVechicle, Tank>();
            //工厂模式注册
            serviceCollection.AddTransient<Driver>(serviceProvider =>
            {
                return new Driver(vechicle: new Tank());
            });
            //可以用已经实例化的对象来注册服务。不过只能在单例模式AddSingleton下使用
            // Driver driverTest = new Driver(vechicle: new Tank());
            // serviceCollection.AddSingleton(typeof(Driver), driverTest);
            //覆盖掉上面的Driver注册
            serviceCollection.AddTransient<Driver>();
            //【6】带键的服务:注入的时候使用别名Servicekey,经常应用于同一个接口注册多个实例
            //键不仅可以为字符串，可以是任何对象，带key的服务和不带key的注册时不会相互覆盖
            serviceCollection.AddKeyedScoped<IVechicle, Tank>("A");
            serviceCollection.AddKeyedScoped<IVechicle, Car>("B");
            serviceCollection.AddKeyedSingleton<Car>("A", (sp, key) => new Car());
            serviceCollection.AddKeyedSingleton<Car>("B", (sp, key) => new Car());
            // 使用时指定键
            // public Driver([FromKeyedServices("A")] IVechicle vechicle);
            //【3】生成容器，初始化服务。默认不加参数。
            IServiceProvider services = serviceCollection.BuildServiceProvider(options: new ServiceProviderOptions()
            {
                //构建时验证依赖，防止运行时才发现依赖缺失。
                ValidateOnBuild = true,
                //防止在根容器错误解析Scoped服务
                //true只能新建scope从scope.ServiceProvider获取服务，直接获取会报错
                //false时从跟容器直接获取服务会变成单例，虽然不报错但是违背了设计初衷
                ValidateScopes = true,
            });
            //把容器集合设置为只读,避免误操作,一旦生成容器后再添加服务就会报错
            serviceCollection.MakeReadOnly();
            //【4】通过容器进行服务的调用
            //GetService查询不到服务会返回Null;GetRequiredService则会报错;
            //如果同一接口注册了多个不同实现，GetServices返回所有实现，GetService返回最后注册的那个
            //下面例子会报错，因为ValidateScopes=true时采用AddScoped注册的服务只能在作用域服务scopeService中获取
            var vertical = services.GetRequiredService<IVechicle>();
            //除了自动依赖注入外，也可以直接拿到带键的服务
            Car? car = services.GetKeyedService<Car>("A");
            car?.Run();
            //【5】作用域服务.自己定义的scope是嵌套在根scope内部的
            //如果存在多个构造函数被解析,IOC容器会调用参数最多的那个
            IServiceScopeFactory serviceScopeFactory = services.GetRequiredService<IServiceScopeFactory>();
            using (var scope = serviceScopeFactory.CreateScope())
            {
                //在作用域服务scopeService获取的服务会在{}运行完回收,如果服务实现IDispose接口会自动执行Dispose
                //自动装配:获取Driver的实例时从容器自动装配构造函数需要的IVechicle的实例Tank
                //Tank是采用AddScoped注册的,在作用域服务中是单例,在外面是瞬时的,在多个不同scope是不同对象
                //Driver是AddTransient注册,在任何地方每次请求都是新的对象
                var serviceProvider = scope.ServiceProvider;
                System.Console.WriteLine("scopedService:------");
                Driver driver3 = serviceProvider.GetRequiredService<Driver>();
                driver3.Disply();//返回IVechicle哈希值
                Driver driver4 = serviceProvider.GetRequiredService<Driver>();
                driver4.Disply();//与上面IVechicle哈希值一致
            }
        }
        //依赖倒置：高层模块不直接依赖于低层模块，而是依赖于抽象，低层模块去实现抽象
        interface IVechicle { void Run(); }
        class Car : IVechicle, IDisposable
        {
            public void Run() => Console.WriteLine("car run!");
            public void Dispose()
            {
                System.Console.WriteLine($"dispose Car:{this.GetHashCode()}");
            }
        }
        class Tank : IVechicle, IDisposable
        {
            public void Run() => Console.WriteLine("tank run!");
            public void Dispose()
            {
                System.Console.WriteLine($"dispose Tank:{this.GetHashCode()}");
            }
        }
        class Driver : IDisposable
        {
            public int? ID { get; set; }
            private IVechicle _vechicle;
            //[FromKeyedServices("A")]标记为A后,自动装配时依赖注入带Key的服务
            public Driver([FromKeyedServices("A")] IVechicle vechicle) => this._vechicle = vechicle;
            public void Drive() => this._vechicle.Run();
            public void Dispose()
            {
                System.Console.WriteLine($"dispose Driver:{this.GetHashCode()}");
            }
            public void Disply()
            {
                System.Console.WriteLine($"{this._vechicle.GetHashCode()}");
            }
        }
    }
}

